// Copyright © 2023 Cisco Systems, Inc. and its affiliates.
// All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package clam

import (
	"strconv"
	"strings"

	log "github.com/sirupsen/logrus"

	"github.com/openclarity/openclarity/scanner/families/malware/clam/constants"
	"github.com/openclarity/openclarity/scanner/families/malware/types"
)

func parseMalwareScanOutput(clamOutput string) ([]types.Malware, *types.ScanSummary) {
	log.Debugf("Parsing clam output: %s", clamOutput)
	lines := strings.Split(clamOutput, "\n")

	malwareInfoList := []types.Malware{}
	for _, line := range lines {
		if strings.Contains(line, constants.ScanSummaryText) {
			break
		}

		// Check if the line ends with "FOUND" to identify malware detections
		if !strings.Contains(line, constants.MalwareDetectedIndication) {
			continue
		}

		words := strings.Fields(line)

		// If the line has less than 3 words in it, it can't be a malware detection, so it's skipped
		if len(words) < constants.DetectedMalwareLineWordCount {
			log.Debugf("omitting invalid clam line: %s", words)
			continue
		}

		detectedMalware := extractDetectedMalware(words)

		malwareInfoList = append(malwareInfoList, *detectedMalware)
	}

	summary := parseScanSummary(clamOutput)

	return malwareInfoList, summary
}

func parseScanSummary(clamSummary string) *types.ScanSummary {
	scanSummary := &types.ScanSummary{}
	if clamSummary == "" {
		return scanSummary
	}

	lines := strings.Split(clamSummary, "\n")
	for _, line := range lines {
		line = strings.TrimSpace(line)
		if line == constants.ScanSummaryTextField {
			break
		}

		parseField(line, scanSummary)
	}

	return scanSummary
}

func parseField(line string, scanSummary *types.ScanSummary) {
	const colonSplitLimit = 2
	fields := strings.SplitN(line, constants.FieldSeparator, colonSplitLimit)
	if len(fields) != colonSplitLimit {
		return
	}

	switch field, value := strings.TrimSpace(fields[0]), strings.TrimSpace(fields[1]); field {
	case constants.KnownVirusesField:
		scanSummary.KnownViruses, _ = strconv.Atoi(value)
	case constants.EngineVersionField:
		scanSummary.EngineVersion = value
	case constants.ScannedDirectoriesField:
		scanSummary.ScannedDirectories, _ = strconv.Atoi(value)
	case constants.ScannedFilesField:
		scanSummary.ScannedFiles, _ = strconv.Atoi(value)
	case constants.InfectedFilesField:
		scanSummary.InfectedFiles, _ = strconv.Atoi(value)
	case constants.DataScannedField:
		scanSummary.DataScanned = value
	case constants.DataReadField:
		scanSummary.DataRead = value
	case constants.TimeTakenField:
		scanSummary.TimeTaken = value
	}
}

func extractDetectedMalware(words []string) *types.Malware {
	if len(words) == 0 {
		return nil
	}
	// Extract the file path from the words
	filePath := strings.TrimSuffix(words[constants.DetectedMalwarePathPosition], ":")

	// Extract the malware name and type from the words
	malwareDesc := words[constants.DetectedMalwareDescriptionPosition]
	splitMalwareDesc := strings.SplitN(malwareDesc, ".", constants.MaxMalwareDescriptionSections)

	malwareTypeIndex := 0
	if len(splitMalwareDesc) == constants.MaxMalwareDescriptionSections {
		malwareTypeIndex = constants.LongMalwareTypePosition
	}

	malwareTypeRaw := splitMalwareDesc[malwareTypeIndex]
	malwareTypeStr := strings.ToUpper(malwareTypeRaw)

	malwareName := splitMalwareDesc[len(splitMalwareDesc)-1]

	return &types.Malware{
		MalwareName: malwareName,
		MalwareType: malwareTypeStr,
		Path:        filePath,
	}
}
